#__docformat__ = "restructuredtext en"
# ******NOTICE***************
# dataops.py module by Shoibal Chakravarty
#
# You may copy and use this module as you see fit with no
# guarantee implied provided you keep this notice in all copies.
# *****END NOTICE************
"""
Module dataops contains various container classes used to initialize, organize 
and store the data used in the simulations.
"""

__author__ = "Shoibal Chakravarty (shoibalc@princeton.edu)"
__version__ = " 0.1"
__date__ = "2011/August"
__license__ = "BSD"

__all__ = ["regioncommon","initialize","datainit","commondata","region","world",\
        "alldata","cg2"]
import numpy as np
import CommentedFile





class regioncommon:
    """
    The regioncommon class is a container that holds the initial data and common parameters 
    and functions for each region.
    
    Example:

    R = regioncommon() 
    constructs a new empty instance of the class. The empty instance is populated with data 
    when passed to class datainit which reads the data from an input csv data file.
    
    Contents after call to datainit:
    
    name: string
        name of the regionclass
    T:  int
        number of periods
    E:  float
        vector of total energy demand (length T+1, 
        initial + number of periods)
    B: float
        vector of region's preference factor for 
        green energy (length T)
    G0: float
        initial green energy production in the region
    cn: float
        vector of levelized cost of new fossil energy 
        in $/MWh (length T)
    co: float
        vector of levelized cost of operating old 
        fossil energy in $/MWh (length T)
    M: float
        vector of max fraction of this period's energy demand that 
        can be replaced by green energy (length T).
    """

    def __init__(self):
        self.name = None
        self.T = None
        self.E = None
        self.B = None
        self.G0 = None
        self.cn = None
        self.co = None
        self.M = None





def initialize():
    """
    utility function to construct 3 empty instances of 
    the regionclass for the 3 regions.
    
    Example:
    
    r1,r2,r3 = initialize()
    """
    return (regioncommon(), regioncommon(), regioncommon())





class datainit:

    """
    Class datainit is a container class that reads a data file and populate
    the empty regioncommon containers with initial data. It also
    reads the common data for commondata class. The data file is a 
    csv file in a prescribed format: see documentation or example in the data 
    directory.
    
    Example:
    
    initialdata = datainit(r1,r2,r3,file,Commentstring='#',Delimiter=',')
    
    r1, r2 and r3 are regioncommon instances and file is a filepointer to 
    csv data file. The default Commentstring ('#') and csv file Delimiter (',')
    can be changed if required.
    
    More elaborate example:
    
    r1,r2,r3 = initialize()
    
    file = open('data.csv', 'r')
    
    initialdata = datainit(r1,r2,r3, file)
    
    Contents:
    
    DATA FOR THE commondata class (see commondata 
    documentation)
    
    DATA for the three regioncommon instances (see 
    regioncommon documentation)
    """

    def __init__(self,r1,r2,r3,filename,Commentstring="#",Delimiter=','):
#        f = open(filename,'rb')
        R = CommentedFile.csv.reader(CommentedFile.removecomments(filename,commentstring=Commentstring),delimiter=Delimiter)
        a = R.next()
        self.discount = float(a[1])
        a = R.next()
        self.T=  int(a[1])
        r1.T = r2.T = r3.T = self.T
        a = R.next()
        self.years = int(a[1])
        a = R.next()
        self.cg0 = float(a[1])
        a = R.next()
        b = int(a[1])
        x = []
        for i in range(b):
            x.append((float(a[2+2*i]),float(a[3+2*i])))
        self.greenpar = tuple(x)
        self.__update__(r1,R)
        self.__update__(r2,R)
        self.__update__(r3,R)
    def __update__(self,r,R):
        a= R.next()
        r.name = a[0]
        r.G0 = float(a[2])
        a = R.next()
        r.E = np.array(a[2:r.T+3],dtype=float)
        a = R.next()
        r.B = np.array(a[2:r.T+2],dtype=float)
        a = R.next()
        r.cn = np.array(a[2:r.T+2],dtype=float)
        a = R.next()
        r.co = np.array(a[2:r.T+2],dtype=float)
        a = R.next()
        r.M = np.array(a[2:r.T+2],dtype=float)
    def __del__(self):
        print "Finished populating data. Exiting."





class commondata:
    """
    Class commondata is a container class that holds the global 
    common data that is used by all scenarios. call with a properly 
    constructed datainit object to construct and populate the instance
    
    Example:

    com = commondata(datainit)

    Contents:
    
    discount: float
        discount rate (annual).
    T:  int
        no. of periods.
    years: int 
        no. of years per period.
    RR: float
        vector of discount factor for every period.
    cg0: float
        initial levelized cost of green energy ($/MWh).
    greenpar: tuple
        Tuple of pairs consisting of (no. of doublings, learning rate).
        for the cost of green energy under learning by doing.
        See function src.dataops.cg2
    """
    def __init__(self,dataobj):
        self.discount = dataobj.discount
        self.T = dataobj.T
        self.years = dataobj.years
        self.RR = np.r_[[1],self.T*[(1-self.discount)**self.years]].cumprod()
        self.cg0 = dataobj.cg0
        self.greenpar = dataobj.greenpar





class region:
    """
    The region class is a container class that holds the initial data 
    and common parameters read from the appropriate regioncommon instance,
    and results of the simulations for various scenarios.
    
    Example:
        CHN1 = region(CHINA,"MYOPIC")
    
    Contents:
    
    name: string
        name of the regionclass
    scenario: string
        name of the scenario being simulated (e.g., Myopic, Two Nash,  etc.)
    descriptor: string
        describes the region's strategy in the scenario (e.g., myopic, Nash etc)
    T:  int
        number of periods
    E:  float
        vector of total energy demand (length T+1, 
        initial + number of periods)
    e:  float
        vector of incremental energy demand (length T)
    G:  float
        vector of total green energy production (length T+1, 
        initial + number of periods)
    g:  float
        vector of incremental green energy production (length T)
    g2:  float
        vector of incremental green energy production in 
        the 2nd period of a two period case (length T-1)
    F:  float
        vector of total fossil energy production (length T+1, 
        initial + number of periods)
    f:  float
        vector of incremental fossil energy production,
        -ve values refer to  retirements (length T)
    cn: float
        vector of levelized cost of new fossil energy in 
        $/MWh (length T)
    co: float
        vector of levelized cost of operating old fossil energy 
        in $/MWh (length T)
    B: float
        vector of region's preference factor for 
        green energy (length T)
    u1: float
        vector of global green utility in current period 
        (length T)
    c1: float
        vector of new global green subsidy in current period 
        (length T)
    C1: float
        vector of global green subsidy in current period 
        (length T)
    u2: float
        vector of global green utility in 2nd period of a 2 period case
        (length T)
    c2: float
        vector of new global green subsidy in 2nd  period of a 2 period case
        (length T)
    M: float
        vector of max fraction of this period's energy demand that 
        can be replaced by green energy (length T).
    m: float
        vector of max new green energy allowed in the current period(length T).
    """
    def __init__(self, init, scenario):
        self.name = init.name
        self.scenario = scenario
        self.descriptor = ''
        if len(init.E)!=(init.T+1) or len(init.cn)!=len(init.co)!=len(init.B)!=init.T:
            raise ValueError("Check initial data: len(E) should be T + 1 and len(cn),len(co), and len(B) should be T.")
        # ENERGY VECTORS
        print "Initializing %s for scenario %s" % (self.name, self.scenario)
        T = self.T = init.T
        self.E = np.r_[init.E]                       # total energy demand (0, initial)
        self.e = np.diff(self.E)                # incremental energy demand
        self.G = np.r_[init.G0*self.E[0],T*[0.0]]    # total green energy (0, initial)
        self.g = np.r_[T*[0.0]]                 # incremental green energy in the myopic case or the first period of a two period case
        self.g2 = np.r_[(T-1)*[0.0]]            # incremental green energy in second period of a two period case
        self.F = np.r_[(1-init.G0)*self.E[0],T*[0.0]]    # total fossil energy (0, initial)
        self.f = np.r_[T*[0.0]]                 # incremental fossil energy in the myopic case or the first period of a two period case (-ve means retirements)
        # COST VECTORS
        self.cn = np.r_[init.cn]                     # levelized cost of new fossil sources
        self.co = np.r_[init.co]                     # levelized cost of operation of old fossil sources
        # PREFERENCE VECTOR
        self.B = np.r_[init.B]                       # preference for green energy
        # UTILITY AND COST PARAMETERS
        self.u1 = np.r_[T*[0.0]]                # utility in myopic case or first period in two period cases
        self.c1 = np.r_[T*[0.0]]                # new subsidy in myopic case or first period in two period cases
        self.C1 = np.r_[T*[0.0]]                # total subsidy in current period
        self.u2 = np.r_[(T-1)*[0.0]]            # utility in second period in two period cases
        self.c2 = np.r_[(T-1)*[0.0]]            # new subsidy in second period in two period cases
        # SOLUTION SEARCH BOUNDS
        self.M = np.r_[init.M]                  # max fraction of current demand that can be replaced by green
        self.m = np.r_[T*[0.0]]                 # max range of search space for green energy (min = 0.0)
    def __del__(self):
        print "Deleting region", self.name, self.scenario





class world:
    """
    The world class holds the global sum of Energy, Fossil Energy and Green Energy.
    The data, parameters and results are public (convenient but poor design choice) but the class
    is being used to define a container for all globally significant data/results for a given scenario.
    combination.

    Call the world class after creating the region classes:

    Example:

        WORLD1 = region(CHN1,EU1,ROW1, "MYOPIC")

    Contents:

    T:  int
        number of periods
    E:  float
        vector of total energy demand (length T+1, 
        initial + number of periods)
    e:  float
        vector of incremental energy demand (length T)
    G:  float
        vector of total green energy production (length T+1, 
        initial + number of periods)
    g:  float
        vector of incremental green energy production (length T)
    cg: float
        vector of levelized cost of green energy in 
        $/MWh (length T+1, initial + no. of periods)
    F:  float
        vector of total fossil energy production (length T+1, 
        initial + number of periods)
    f:  float
        vector of incremental fossil energy production,
        -ve values refer to  retirements (length T)
    u1: float
        vector of global green utility in current period 
        (length T)
    c1: float
        vector of new global green subsidy in current period 
        (length T)
    C1: float
        vector of global green subsidy in current period 
        (length T)
    GS: float
        vector of share of green energy in current
        energy demand (length T+1, initial + no. of periods)
    """
    def __init__(self, r1, r2, r3, com, scenario):
        self.name = 'WORLD'
        self.scenario = scenario
        print "Initializing %s for scenario %s" % (self.name, self.scenario)
        T = self.T = r1.T
        self.E = np.r_[r1.E+r2.E+r3.E]      # total energy demand (0, initial)
        self.e = np.diff(self.E)            # incremental energy demand
        self.G = np.r_[r1.G+r2.G+r3.G]      # total green energy (0, initial)
        self.g = np.r_[T*[0.0]]             # incremental green energy in the myopic case or the first period of a two period case
        self.cg = np.r_[(T+1)*[com.cg0]]    # cost of green energy ($/MWh)
        self.F = np.r_[r1.F+r2.F+r3.F]      # total fossil energy (0, initial)
        self.f = np.r_[T*[0.0]]             # incremental fossil energy in the myopic case or the first period of a two period case, -ve means retirements)
        self.u1 = np.r_[T*[0.0]]            # total global utility in current period
        self.c1 = np.r_[T*[0.0]]            # total new global subsidy in current period
        self.C1 = np.r_[T*[0.0]]            # total global green subsidy
        self.GS = np.r_[self.G/self.E]      # total global green energy share
    def __del__(self):
        print "Deleting", self.name, self.scenario





class alldata(dict):
    """
    Class alldata is a handy dictionary class that holds the three region 
    class instances labeled by keys that are the region names, and one world class instance
    (key: 'WORLD') for each scenario simulation. It's only purpose is to keep all 
    data and results of a scenario simulation in one container for further 
    use (e.g., to print results).

    Contents:

    scenario: string
        name of scenario

    r1: region

    r2: region

    r3: region

    WORLD: world
    """
    def __init__(self,r1,r2,r3,com,scenario):
        dict.__init__(self)
    #    self = dict()
        self.__dict__.update({'scenario':scenario})
        self.__dict__.update({r1.name: region(r1,scenario)})
        self.__dict__.update({r2.name: region(r2,scenario)})
        self.__dict__.update({r3.name: region(r3,scenario)})
        self.__dict__.update({'WORLD': world(region(r1,scenario),region(r2,scenario),region(r3,scenario),com,scenario)})
    def get(self, key):
        """
        exact equivalent of a dictionary get method, returns the appropriate instance 
        of the region or world class.

        Example:

        Myopic.get('CHINA') returns the instance of the region class CHINA. 
        """
        return self.__dict__.get(key)





def cg2(cg0,G0,G,args=()):
    """
    Function cg gives the cost of green energy when total green deployment is G.
    
    Parameters:

    cg0: float
        Initial cost of green energy
    G0: float
        Initial global green energy at time t=0
    G: float
        Current global green energy
    args: tuple
        args is a tuple of pairs containing (no. of doublings, learning rate)
        
        Example for args:
            args = ((3,0.22),(3,0.11),(3,0.01))
            In this case the learning rate is 0.22 for the first 3 doublings (G0<=G<=8.0*G0),
            0.11 for the next three doublings (8.0*G0<=G<=64.0*G0), and 0.01 for the next 
            three doublings. The default assumption is that the learning rate in the last pairs
            continues till the end of the simulation 
        
    Examples for cg(cg0,G0,G,args):
        cg0=2,G0=2,args = ((3,0.2),(4,0.1))

        cg(2,2,2,args) = 2.0

        cg(2,2,4,args) = 1.6

        cg(2,2,9,args) = 1.232374023
    """
    k = len(args) + 1
    GG = np.r_[k*[0.0]]
    cgk = np.r_[k*[1.0]]
    alpha = np.r_[k*[0.0]]
    GG[0], cgk[0] = 0.0, 1.0
    for i in range(k)[1:]:
        GG[i] = GG[i-1] + args[i-1][0]
        alpha[i] = np.log(1-args[i-1][1])/np.log(2.0)
        cgk[i] = cgk[i-1]*2.0**(args[i-1][0]*alpha[i])
    #    print GG[i],alpha[i],cgk[i]
        if np.log(G/G0)/np.log(2.0) <= GG[i]:
     #       print GG[i],alpha[i],cgk[i],cgk[i-1]*(G/(G0*2.0**GG[i-1]))**alpha[i]
            return cg0*cgk[i-1]*(G/(G0*2.0**GG[i-1]))**alpha[i]
    return cg0*cgk[i-1]*(G/(G0*2.0**GG[i-1]))**alpha[i]






